﻿#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;

namespace PHMEE.EnergyEfficiency;

public class LogAbstractFormulas
{
    /// <summary>
    /// EEOI 通用公式
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <param name="vesselType"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static float EEOI(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes, VesselType vesselType)
    {
        var result = EEOINullable(logAbstracts, fuelTypes, vesselType);
        if (result is null)
        {
            // TODO: 本地化异常
            throw new Exception(
                "Within this time period, the interim cargo load or voyage distance is 0, rendering the calculation of EEOI impossible.");
        }

        return result.Value;
    }

    /// <summary>
    /// EEOI 通用公式
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <param name="vesselType"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static float? EEOINullable(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes, VesselType vesselType)
    {
        float numerator = 0f;
        float denominator = 0f;

        foreach (var logAbstract in logAbstracts)
        {
            foreach (var fuelType in fuelTypes)
            {
                var cf = FuelCfMapper.GetFuelCfCO2(fuelType);
                var fuelConsumption = logAbstract.GetTotalWithEmrgConsumptionByFuelType(fuelType);
                numerator += fuelConsumption * cf;
            }

            var mCargo = logAbstract.GetMCargo(vesselType);
            denominator += mCargo * logAbstract.Distance;
        }

        if (denominator == 0)
        {
            return null;
        }

        var result = numerator * 1e6f / denominator;
        return result;
    }

    /// <summary>
    /// 公式 1.3：某段时间的平均每海里油耗，以日为单位，单位 (t/(nm))
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    public static float? CSNullable(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes)
    {
        var denominator = logAbstracts.Sum(la => la.Distance);
        if (denominator == 0)
        {
            return null;
        }

        float numerator = 0f;

        foreach (var fuelType in fuelTypes)
        {
            var fuelConsumption = logAbstracts.GetTotalWithEmrgConsumptionByFuelType(fuelType);
            numerator += fuelConsumption;
        }

        var result = numerator / denominator;
        return result;
    }

    /// <summary>
    /// 公式 1.4.1：某段时间的平均每运输功油耗，以日为单位 (g/(t·nm))
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <param name="vesselType"></param>
    /// <returns></returns>
    public static float? CPNullable(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes, VesselType vesselType)
    {
        float numerator = 0f;
        float denominator = 0f;

        foreach (var logAbstract in logAbstracts)
        {
            foreach (var fuelType in fuelTypes)
            {
                var fuelConsumption = logAbstract.GetTotalWithEmrgConsumptionByFuelType(fuelType);
                numerator += fuelConsumption;
            }

            var mCargo = logAbstract.GetMCargo(vesselType);
            denominator += mCargo * logAbstract.Distance;
        }

        if (denominator == 0)
        {
            return null;
        }

        // 转换为 g/(t·nm)
        var result = (numerator / denominator) * 1e6f;
        return result;
    }

    /// <summary>
    /// 公式 1.5：某些 LogAbstract 的平均每海里 CO2 排放，单位：(t CO2/nm)
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    public static float? ESNullable(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes)
    {
        var denominator = logAbstracts.Sum(la => la.Distance);

        if (denominator == 0)
        {
            return null;
        }

        float numerator = 0f;

        foreach (var fuelType in fuelTypes)
        {
            var cf = FuelCfMapper.GetFuelCfCO2(fuelType);
            var fuelConsumption = logAbstracts.GetTotalWithEmrgConsumptionByFuelType(fuelType);
            numerator += fuelConsumption * cf;
        }

        var result = numerator / denominator;
        return result;
    }

    /// <summary>
    /// 公式 1.5 变种，计算的是二氧化碳当量，而不仅仅是二氧化碳，用于 MRV EU 报表：某些 LogAbstract 的平均每海里 CO2 当量排放，单位 (t CO2eq/nm)
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <param name="gwpN2O"></param>
    /// <param name="gwpCH4"></param>
    /// <returns></returns>
    public static float? ESNullableForMRV(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes, float gwpN2O,
        float gwpCH4)
    {
        var denominator = logAbstracts.Sum(la => la.Distance);

        if (denominator == 0)
        {
            return null;
        }

        float numerator = 0f;

        foreach (var fuelType in fuelTypes)
        {
            var cfCO2 = FuelCfMapper.GetFuelCfCO2(fuelType);
            var cfN2O = FuelCfMapper.GetFuelCfN2O(fuelType);
            var cfCH4 = FuelCfMapper.GetFuelCfCH4(fuelType);
            var fuelConsumption = logAbstracts.GetTotalWithEmrgConsumptionByFuelType(fuelType);
            numerator += fuelConsumption * (cfCO2 + (gwpN2O * cfN2O) + (gwpCH4 * cfCH4));
        }

        var result = numerator / denominator;
        return result;
    }

    /// <summary>
    /// 公式 1.6：某段时间的平均每运输单位 CO2 排放，以日为单位，单位 (g CO2/(t*nm))
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <param name="vesselType"></param>
    /// <returns></returns>
    public static float? ECNullable(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes, VesselType vesselType)
    {
        return EEOINullable(logAbstracts, fuelTypes, vesselType);
    }

    /// <summary>
    /// 公式 1.6 变种，计算的是二氧化碳当量，而不仅仅是二氧化碳，用于 MRV EU 报表：某段时间的平均每运输单位 CO2 当量排放，以日为单位，单位 (g CO2eq/(t*nm))
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <param name="vesselType"></param>
    /// <param name="gwpN2O"></param>
    /// <param name="gwpCH4"></param>
    /// <returns></returns>
    public static float? ECNullableForMRV(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes,
        VesselType vesselType, float gwpN2O, float gwpCH4)
    {
        float numerator = 0f;
        float denominator = 0f;

        foreach (var logAbstract in logAbstracts)
        {
            foreach (var fuelType in fuelTypes)
            {
                var cfCO2 = FuelCfMapper.GetFuelCfCO2(fuelType);
                var cfN2O = FuelCfMapper.GetFuelCfN2O(fuelType);
                var cfCH4 = FuelCfMapper.GetFuelCfCH4(fuelType);
                var fuelConsumption = logAbstract.GetTotalWithEmrgConsumptionByFuelType(fuelType);
                numerator += fuelConsumption * (cfCO2 + (gwpN2O * cfN2O) + (gwpCH4 * cfCH4));
            }

            var mCargo = logAbstract.GetMCargo(vesselType);
            denominator += mCargo * logAbstract.Distance;
        }

        if (denominator == 0)
        {
            return null;
        }

        var result = numerator * 1e6f / denominator;
        return result;
    }

    /// <summary>
    /// CII 通用公式
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="capacity"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static float CII(List<LogAbstract> logAbstracts, float capacity, List<FuelType> fuelTypes)
    {
        var result = CIINullable(logAbstracts, capacity, fuelTypes);
        if (result is null)
        {
            throw new Exception(
                "The ship's carrying capacity is 0 or the voyage distance is 0, CII calculation cannot be performed");
        }

        return result.Value;
    }

    /// <summary>
    /// CII 通用公式
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="capacity"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static float? CIINullable(List<LogAbstract> logAbstracts, float capacity, List<FuelType> fuelTypes)
    {
        float denominator = capacity * logAbstracts.Sum(la => la.Distance);

        if (denominator == 0)
        {
            return null;
        }

        float numerator = 0f;

        foreach (var fuelType in fuelTypes)
        {
            var cf = FuelCfMapper.GetFuelCfCO2(fuelType);
            var fuelConsumption = logAbstracts.GetTotalWithEmrgConsumptionByFuelType(fuelType);
            numerator += fuelConsumption * 1e6f * cf;
        }

        var cii = numerator / denominator;
        return cii;
    }

    /// <summary>
    /// CO2 排放量（已修正）：实际是 CII 修正公式的分子部分，单位为 g
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="adjustedLogAbstracts"></param>
    /// <param name="stsLogAbstracts"></param>
    /// <param name="yi"></param>
    /// <param name="ftTanker"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    public static float CO2EmissionsCorrect(List<LogAbstract> logAbstracts,
        List<LogAbstract> adjustedLogAbstracts,
        List<LogAbstract> stsLogAbstracts,
        float yi, float ftTanker,
        List<FuelType> fuelTypes)
    {
        // 数据量少，速度足够，无需将 List 转为 HashSet 来追求微末的性能提升
        float numerator = 0f;

        foreach (var logAbstract in logAbstracts)
        {
            foreach (var fuelType in fuelTypes)
            {
                var cf = FuelCfMapper.GetFuelCfCO2(fuelType);

                // Qe,j,ship
                var totalFuelConsumption = logAbstract.GetTotalWithEmrgConsumptionByFuelType(fuelType);
                
                float adjustFuelConsumption = 0f;
                // 如果是调整后的 log abstract，那么需要计算 Qe,voyage,j,ship
                if (adjustedLogAbstracts.Contains(logAbstract))
                {
                    adjustFuelConsumption = totalFuelConsumption;
                    // adjustFuelConsumption = logAbstracts.GetTotalConsumptionByFuelType(fuelType);
                }
                
                float stsFuelConsumption = 0f;
                // 如果是 STS log abstract，那么需要计算 Qe,sts,j,ship
                if (stsLogAbstracts.Contains(logAbstract))
                {
                    stsFuelConsumption = totalFuelConsumption;
                    // stsFuelConsumption = logAbstracts.GetTotalConsumptionByFuelType(fuelType);
                }
                
                // (1-FTTanker) * Qe,sts,j,ship
                float stsFuelConsumptionWithFTTanker = (1 - ftTanker) * stsFuelConsumption;

                float fcDElectricalJ = 0f;
                float fcDBoilerJ = 0f;
                float fcDOtherJ = 0f;
                // 判断 stsFuelConsumptionWithFTTanker 是否小于等于 0，如果是，则需要计算 fcD
                if (IsFloatLessThanOrEqualToZero(stsFuelConsumptionWithFTTanker))
                {
                    fcDElectricalJ = GetFCdElectricalJ(logAbstract, fuelType);
                    fcDBoilerJ = GetFCdBoilerJ(logAbstract, fuelType);
                    fcDOtherJ = GetFCdOtherJ(logAbstract, fuelType);
                }
                
                numerator += cf * (totalFuelConsumption -
                                   (adjustFuelConsumption + stsFuelConsumptionWithFTTanker +
                                    (0.75f - 0.03f * yi) *
                                    (fcDElectricalJ + fcDBoilerJ + fcDOtherJ))) * 1e6f;
            }
        }

        return numerator;
    }
    
    /// <summary>
    /// 考虑到浮点数精度问题的 float 类型的比较，是否小于等于 0
    /// </summary>
    /// <param name="value"></param>
    /// <param name="epsilon"></param>
    /// <returns></returns>
    private static bool IsFloatLessThanOrEqualToZero(float value, float epsilon = 1e-6f)
    {
        return value < epsilon;
    }

    /// <summary>
    /// CII（修正后）公式，单位为 gCO2/(t*nm)
    /// </summary>
    /// <param name="logAbstracts">所有区间内的 log abstracts</param>
    /// <param name="adjustedLogAbstracts">所有区间内的调整的 log abstracts</param>
    /// <param name="stsLogAbstracts">所有区间内的 STS log abstracts</param>
    /// <param name="yi"></param>
    /// <param name="capacity"></param>
    /// <param name="fi"></param>
    /// <param name="fm"></param>
    /// <param name="fc"></param>
    /// <param name="fiVSE"></param>
    /// <param name="ftTanker"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    public static float? CIICorrectNullable(List<LogAbstract> logAbstracts,
        List<LogAbstract> adjustedLogAbstracts,
        List<LogAbstract> stsLogAbstracts,
        float yi, float capacity, float fi, float fm, float fc, float fiVSE, float ftTanker,
        List<FuelType> fuelTypes)
    {
        float denominator = fi * fm * fc * fiVSE * capacity *
                            (logAbstracts.Sum(la => la.Distance) - adjustedLogAbstracts.Sum(ala => ala.Distance));

        if (denominator == 0)
        {
            return null;
        }

        var numerator =
            CO2EmissionsCorrect(logAbstracts, adjustedLogAbstracts, stsLogAbstracts, yi, ftTanker, fuelTypes);

        return numerator / denominator;
    }

    /// <summary>
    /// 传入所有 log abstracts，获取 “出于以下目的，某日用于生产电力所消耗的j型燃料  a,运载冷藏集装箱船的冷藏集装箱的电力消耗：b,气体运输船和液化天然气船货物冷却/再液化系统的电力消耗”
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelType"></param>
    /// <returns></returns>
    private static float GetFCdElectricalJ(List<LogAbstract> logAbstracts, FuelType fuelType)
    {
        // Pele * SFOC
        float peleJMulSFOC = 0f;

        foreach (var logAbstract in logAbstracts)
        {
            peleJMulSFOC += GetFCdElectricalJ(logAbstract, fuelType);
        }

        return peleJMulSFOC * 1e-6f;
    }

    private static float GetFCdElectricalJ(LogAbstract logAbstract, FuelType fuelType)
    {
        // Pele * SFOC
        float peleJMulSFOC = 0f;

        if (logAbstract.ReeferFuelType == fuelType)
        {
            peleJMulSFOC += logAbstract.ReeferWork * logAbstract.ReeferSFOC;
        }

        if (logAbstract.CargoCoolingFuelType == fuelType)
        {
            peleJMulSFOC += logAbstract.CargoCoolingWork * logAbstract.CargoCoolingSFOC;
        }

        if (logAbstract.DischargePumpFuelType == fuelType)
        {
            peleJMulSFOC += logAbstract.DischargePumpWork * logAbstract.DischargePumpSFOC;
        }

        return peleJMulSFOC * 1e-6f;
    }

    /// <summary>
    /// 传入所有 log abstracts，获取 “对于配有燃油锅炉用于货物加热或蒸汽驱动货泵的液货船，某日用于货物加热或卸货泵运行所消耗的j型燃料量c,液货船卸货泵的电力消耗”
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelType"></param>
    /// <returns></returns>
    private static float GetFCdBoilerJ(List<LogAbstract> logAbstracts, FuelType fuelType)
    {
        return logAbstracts.GetCargoHeatingConsumptionByFuelType(fuelType);
    }

    private static float GetFCdBoilerJ(LogAbstract logAbstract, FuelType fuelType)
    {
        return logAbstract.GetCargoHeatingConsumptionByFuelType(fuelType);
    }

    /// <summary>
    /// 传入所有 log abstracts，获取 “对于货泵是由独立发动机驱动的液货船，货泵某日在工作期间消耗的j型燃料的质量”
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelType"></param>
    /// <returns></returns>
    private static float GetFCdOtherJ(List<LogAbstract> logAbstracts, FuelType fuelType)
    {
        return logAbstracts.GetDPPCargoPumpConsumptionByFuelType(fuelType);
    }

    private static float GetFCdOtherJ(LogAbstract logAbstract, FuelType fuelType)
    {
        return logAbstract.GetDPPCargoPumpConsumptionByFuelType(fuelType);
    }

    public static float GetYi(int year)
    {
        if (year < 2023)
        {
            return 0;
        }

        return year - 2023;
    }

    /// <summary>
    /// 剩余年度要达到的 CII - CII 优化建议
    /// </summary>
    /// <param name="today"></param>
    /// <param name="yearStartDate"></param>
    /// <param name="ciiFinished">已航行天数的平均 CII，用通用 CII 公式计算</param>
    /// <param name="ciiMax">年度期望的 CII 评级边界</param>
    /// <returns></returns>
    public static float? OptimizeCIISuggestionNullable(DateTime today,
        DateTime yearStartDate, float ciiFinished, float ciiMax)
    {
        // 本年度已经航行的天数（自然年已经过去的天数）
        int dayf = (today - yearStartDate).Days;

        // 今年有多少天
        int thisYearDays = DateTime.IsLeapYear(today.Year) ? 366 : 365;

        // 预计本年度需要航行而未航行的天数（自然年未过去的天数）
        int dayw = thisYearDays - dayf;

        if (dayw == 0)
        {
            return null;
        }

        // 剩余年度要达到的 CII
        var ciiPre = ((dayf + (float)dayw) / dayw) * ciiMax - (ciiFinished * dayf) / dayw;
        return ciiPre;
    }

    /// <summary>
    /// 公式 2.7 累计 CO2 排放量
    /// </summary>
    /// <param name="logAbstracts"></param>
    /// <param name="fuelTypes"></param>
    /// <returns></returns>
    public static float CO2Emissions(List<LogAbstract> logAbstracts, List<FuelType> fuelTypes)
    {
        float result = 0;

        foreach (var fuelType in fuelTypes)
        {
            var cf = FuelCfMapper.GetFuelCfCO2(fuelType);
            var fuelConsumption = logAbstracts.GetTotalWithEmrgConsumptionByFuelType(fuelType);
            result += fuelConsumption * cf;
        }

        return result;
    }

    // /// <summary>
    // /// 公式 6.1 CII 基线
    // /// </summary>
    // /// <param name="vesselType"></param>
    // /// <param name="capacity"></param>
    // /// <param name="dwt"></param>
    // /// <param name="gt"></param>
    // /// <returns></returns>
    // public static float CIIRef(VesselType vesselType, float capacity, float dwt, float gt)
    // {
    //     var ciiBaselineParameters = VesselTypeHelper.GetCIIBaselineParameters(vesselType, dwt, gt);
    //
    //     return ciiBaselineParameters.A * (float)Math.Pow(capacity, -ciiBaselineParameters.C);
    // }
    //
    // /// <summary>
    // /// 公式 6.2 要求年度 CII
    // /// </summary>
    // /// <param name="ciiRef"></param>
    // /// <param name="z"></param>
    // /// <returns></returns>
    // public static float RequiredCII(float ciiRef, float z)
    // {
    //     return (1 - (z / 100)) * ciiRef;
    // }
}